package com.ouroboroswiki.core.content.xslt;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.io.output.ByteArrayOutputStream;

import com.ouroboroswiki.core.AbstractContentRepository;
import com.ouroboroswiki.core.Content;
import com.ouroboroswiki.core.ContentException;
import com.ouroboroswiki.core.ContentPath;
import com.ouroboroswiki.core.ContentRepository;
import com.ouroboroswiki.core.ContentUtil;
import com.ouroboroswiki.core.MultipartVersion;
import com.ouroboroswiki.core.Version;
import com.ouroboroswiki.core.VersionBuilder;

public class XSLTContentRepository extends AbstractContentRepository {
	
	private static final Logger log = Logger.getLogger(XSLTContentRepository.class.getName());
	
	public static final String XSL_CONTENT = "xsl";
	public static final String PARAMETER_CONTENT = "parameters";
	
	private ContentRepository xmlRepository;
	private ContentRepository xslRepository;
	private ContentRepository propertiesRepository;
	private String mimeType;
	private Map<String, Object> globalProperties;
	private ContentRepository uriRepository;
	
	public XSLTContentRepository(
			String name, 
			String mimeType,
			Map<String, Object> globalProperties) {
		this( name, null, null, null, mimeType, globalProperties );
	}
	
	public XSLTContentRepository( 
			String name,
			ContentRepository xmlRepository, 
			ContentRepository xslRepository, 
			ContentRepository propertiesRepository, 
			String mimeType,
			Map<String, Object> globalProperties
	) {
		this.setName(name);
		this.xmlRepository = xmlRepository;
		this.xslRepository = xslRepository;
		this.propertiesRepository = propertiesRepository;
		this.mimeType = mimeType;
		this.globalProperties = globalProperties;
	}
	
	public ContentRepository getUriRepository() {
		return uriRepository;
	}

	public void setUriRepository(ContentRepository uriRepository) {
		this.uriRepository = uriRepository;
	}

	public ContentRepository getXmlRepository() {
		return xmlRepository;
	}

	public void setXmlRepository(ContentRepository xmlRepository) {
		this.xmlRepository = xmlRepository;
	}

	public ContentRepository getXslRepository() {
		return xslRepository;
	}

	public void setXslRepository(ContentRepository xslRepository) {
		this.xslRepository = xslRepository;
	}

	public ContentRepository getPropertiesRepository() {
		return propertiesRepository;
	}

	public void setPropertiesRepository(ContentRepository propertiesRepository) {
		this.propertiesRepository = propertiesRepository;
	}

	@Override
	public boolean exists(Object principal, ContentPath path) throws ContentException {
		return xmlRepository.exists(principal, path) && xslRepository.exists(principal, path);
	}

	@Override
	public Content getContent(final Object principal, ContentPath path, final Version version, final Map<String, Object> properties)
			throws ContentException {
		MultipartVersion multipartVersion = (MultipartVersion)version;
		Version xmlVersion;
		Version xslVersion;
		Version propertiesVersion;
		if( multipartVersion != null ) {
			Map<String, Version> versionMap = multipartVersion.getVersions();
			xmlVersion = versionMap.get(Content.CHILD_CONTENT_MAIN);
			xslVersion = versionMap.get(XSL_CONTENT);
			propertiesVersion = versionMap.get(PARAMETER_CONTENT); 
		} else {
			xmlVersion = null;
			xslVersion = null;
			propertiesVersion = null;
		}
		Content xmlContent = xmlRepository.getContent(principal, path, xmlVersion, properties);
		Content xslContent = xslRepository.getContent(principal, path, xslVersion, properties);
		Content propertiesContent = propertiesRepository.getContent(principal, path, propertiesVersion, properties);

		final XSLTContent content = makeContent(xmlContent, xslContent, propertiesContent, path, properties);
		URIResolver uriResolver = new URIResolver() {
			
			@Override
			public Source resolve(String href, String base) throws TransformerException {
				String userDir = normalize(System.getProperty("user.dir"));
				base = normalize(base);
				if( base.startsWith( userDir ) ) {
					base = base.substring( userDir.length() );
					if( base.startsWith("/") ) {
						base = base.substring(1);
					}
				}
				StreamSource result;
				// obtain the path from the href and the base
				// trim out the last '/' from the base
				String dir;
				if( href.startsWith("/" ) ) {
					dir = "";
				} else {
					int lastSlash = base.lastIndexOf('/');
					if( lastSlash >= 0 ) {
						dir = base.substring(0, lastSlash+1);
					} else {
						dir = "";
					}
				}
				//deal with ../ paths
				boolean done;
				do {
					if( href.startsWith("../") ) {
						int dirIndex = -1;
						for( int i=dir.length()-1; i>0; ) {
							i--;
							if( dir.charAt(i) == '/' ) {
								dirIndex = i;
								break;
							}
						}
						if( dirIndex >= 0 ) {
							href = href.substring(3);
							dir = dir.substring(0, dirIndex+1);
						}
						done = false;
					} else if( href.startsWith("./") ) {
						href = href.substring(2);
						done = false;
					} else {
						done = true;
					}
				} while( !done );
				String file = dir + href;
				ContentPath path = ContentUtil.singleFromString(file);
				String name = href;
				try {
					Version childVersion;
					if( version instanceof MultipartVersion ) {
						MultipartVersion multipartVersion = (MultipartVersion)version;
						childVersion = multipartVersion.getVersions().get(name);
					} else {
						childVersion = null;
					}
					Content uriContent = uriRepository.getContent(principal, path, childVersion, properties);
					// remember to add the content to the child content of the content
					content.addLoadedContent(href, uriContent);
					ByteArrayOutputStream bos = new ByteArrayOutputStream();
					uriContent.write(bos);
					result = new StreamSource( new ByteArrayInputStream(bos.toByteArray()), uriContent.getUniqueName()+"."+uriContent.getRepositoryName());
				} catch( Exception ex ) {
					log.log( Level.SEVERE, "unable to load "+href+" from "+base +"("+file+")", ex );
					result = null;
				}
				return result;
			}
		};
		content.setUriResolver(uriResolver);
		TransformerFactory transformerFactory = TransformerFactory.newInstance();
		transformerFactory.setURIResolver(uriResolver);
		content.setTransformerFactory(transformerFactory);
		// generated on the spot and cached because the child content wont be populated until it is 
		try {
			return ContentUtil.pushToStaticContent(content);
		} catch( IOException ex ) {
			throw new ContentException( "unable to create content", ex );
		}
	}
	
	protected XSLTContent makeContent( Content xmlContent, Content xslContent, Content propertiesContent, ContentPath path, Map<String, Object> properties ) {
		XSLTContent content = makeContent( xmlContent, xslContent, propertiesContent );
		content.setMimeType(mimeType);
		content.setUniqueName( ContentUtil.toString(path) );
		HashMap<String, Object> newProperties = new HashMap<String, Object>();
		if( globalProperties != null ) {
			newProperties.putAll(globalProperties);
		}
		if( properties != null ) {
			// turn all the properties into strings
			for( Map.Entry<String, Object> entry : properties.entrySet() ) {
				Object value = entry.getValue();
				if( value instanceof String[] || value instanceof Object[] ) {
					int length = Array.getLength(value);
					StringBuffer sb = new StringBuffer();
					for( int i=0; i<length; i++ ) {
						sb.append( Array.get(value, i) );
						if( i < length-1 ) {
							sb.append(',');
						}
					}
					newProperties.put( entry.getKey(), sb.toString() );
				} else {
					newProperties.put( entry.getKey(), value );					
				}
			}
			//newProperties.putAll(properties);
		}
		content.setGlobalProperties(newProperties);
		return content;
	}
	
	protected XSLTContent makeContent( Content xmlContent, Content xslContent, Content propertiesContent ) {
		return new XSLTContent( xmlContent, xslContent, propertiesContent );
	}

	@Override
	public Version buildVersion(Object principal,ContentPath path, String[] versionPath, VersionBuilder versionBuilder) throws ContentException {

		Version xmlVersion = xmlRepository.buildVersion(principal, path, ContentUtil.append( versionPath, Content.CHILD_CONTENT_MAIN), versionBuilder);
		Version xslVersion = xslRepository.buildVersion(principal, path, ContentUtil.append( versionPath, XSL_CONTENT), versionBuilder);
		Version propertiesVersion = propertiesRepository.buildVersion(principal, path, ContentUtil.append( versionPath, PARAMETER_CONTENT), versionBuilder);
		
		HashMap<String, Version> versions = new HashMap<String, Version>(3);
		versions.put(Content.CHILD_CONTENT_MAIN, xmlVersion);
		versions.put(XSL_CONTENT, xslVersion);
		versions.put(PARAMETER_CONTENT, propertiesVersion);
		
		return new MultipartVersion(versions);
	}
	
	
	private static final String normalize( String s ) { 
		s = s.replace('\\', '/');
		String prefix = "file:/";
		if( s.startsWith(prefix) ) {
			s = s.substring(prefix.length()+1);
		}
		while( s.startsWith("/") ) {
			s = s.substring(1);
		}
		return s;
	}
}
